//@author A0081058H
package net.mysoc.w111j.ui.expenselist;

import net.mysoc.w111j.model.BadCategoryNameException;
import net.mysoc.w111j.model.BadPaymentTypeNameException;
import net.mysoc.w111j.model.Category;
import net.mysoc.w111j.model.Expense;
import net.mysoc.w111j.model.PaymentType;
import net.mysoc.w111j.model.User;
import net.mysoc.w111j.service.ExpenseService;
import net.mysoc.w111j.service.filters.CategoryExpenseFilter;
import net.mysoc.w111j.service.filters.ExpenseFilter;
import net.mysoc.w111j.service.filters.MultiFilter;
import net.mysoc.w111j.service.filters.NullExpenseFilter;
import net.mysoc.w111j.service.filters.PaymentTypeExpenseFilter;
import net.mysoc.w111j.service.filters.TextExpenseFilter;
import net.mysoc.w111j.ui.main.MainPresenter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The presenter for the expense list.
 */
public class ExpenseListPresenter {
    private static Logger logger = LoggerFactory.getLogger(ExpenseListPresenter.class);

    private final ExpenseListModel model;
    private final ExpenseListView view;
    private final ExpenseService expenseService;
    private final MainPresenter mainPresenter;

    /**
     * Constructor. Links the {@code ExpenseListModel}, {@code ExpenseService} and {@code ExpenseListView}
     * to this {@code ExpenseListPresenter}.
     *
     * @param model          the {@code ExpenseListModel} to link.
     * @param view           the {@code ExpenseListView} to link.
     * @param expenseService the {@code ExpenseService} to link.
     */
    public ExpenseListPresenter(ExpenseListModel model, ExpenseListView view, ExpenseService expenseService,
            MainPresenter mainPresenter) {
        this.model = model;
        this.view = view;
        this.view.setPresenter(this);
        this.expenseService = expenseService;
        this.mainPresenter = mainPresenter;

        handleAllCategories(); // Defaults to all expenses shown
    }

    /**
     * Gets the view linked to this {@code ExpenseListPresenter}.
     *
     * @return the view linked to this {@code ExpenseListPresenter}.
     */
    public ExpenseListView getView() {
        return this.view;
    }

    /**
     * Handles the category change event.
     *
     * @param category the category to switch the expense list to.
     */
    public void handleChangeCategory(Category category) {
        logger.debug("Change category to {}", category);
        changeExpenseFilter(new CategoryExpenseFilter(category));
        refresh();
    }

    /**
     * Handles the all expenses event.
     */
    public void handleAllCategories() {
        logger.debug("Change category to all");
        changeExpenseFilter(new NullExpenseFilter());
        refresh();
    }

    /**
     * Handles category creation event.
     *
     * @param category the name of the new category.
     */
    public boolean handleCreateNewCategory(String category) {
        try {
            this.expenseService.updateCategory(new Category(User.NO_ID, category));
        } catch (BadCategoryNameException | IllegalArgumentException e) {
            return false;
        }
        this.view.updateCategoryList();
        this.mainPresenter.refreshAll();
        return true;
    }

    /**
     * Handles the edit category event.
     *
     * @param category The category to edit.
     * @param name The new name for the category.
     * @return whether the name edit was successful.
     */
    public boolean handleEditCategory(Category category, String name) {
        logger.debug("Handle edit category: {} to {}", category, name);
        try {
            this.expenseService.updateCategory(new Category(category.getId(), name));
        } catch (BadCategoryNameException | IllegalArgumentException e) {
            return false;
        }
        this.mainPresenter.refreshAll(); // Need to refresh status as well
        return true;
    }

    /**
     * Handles the delete category event.
     *
     * @param category the category to be deleted.
     */
    public void handleDeleteCategory(Category category) {
        logger.debug("Handle delete category: {}", category);
        this.expenseService.deleteCategory(category.getId());

        // Switch to all expenses if we're deleting the currently viewed category.
        if (this.model.getExpenseFilter().getClass().equals(CategoryExpenseFilter.class)
                && ((CategoryExpenseFilter) this.model.getExpenseFilter()).getCategory().equals(category)) {
            handleAllCategories();
        }
        this.mainPresenter.refreshAll(); // Need to refresh status as well
    }

    /**
     * Handles the payment type change event.
     *
     * @param paymentType the payment type to switch the expense list to.
     */
    public void handleChangePaymentType(PaymentType paymentType) {
        logger.debug("Change payment type to {}", paymentType);
        changeExpenseFilter(new PaymentTypeExpenseFilter(paymentType));
        refresh();
    }

    //@author A0081189X
    /**
     * Handles creation of new {@code PaymentType}s.
     *
     * @param paymentType the name of the new {@code PaymentType}.
     * @return whether creation of a new {@code PaymentType} was successful.
     */
    public boolean handleCreateNewPaymentType(String paymentType) {
        try {
            this.expenseService.updatePaymentType(new PaymentType(User.NO_ID, paymentType));
        } catch (BadPaymentTypeNameException | IllegalArgumentException e) {
            return false;
        }
        this.view.updateCategoryList();
        this.mainPresenter.refreshAll();
        return true;
    }

    /**
     * Handles the editing payment type event.
     *
     * @param paymentType the {@code PaymentType} to be edited.
     * @param name the new name for the payment type.
     * @return whether the payment type was edited.
     */
    public boolean handleEditPaymentType(PaymentType paymentType, String name) {
        logger.debug("Handle edit payment type: {} to {}", paymentType, name);
        try {
            this.expenseService.updatePaymentType(new PaymentType(paymentType.getId(), name));
        } catch (BadPaymentTypeNameException | IllegalArgumentException e) {
            return false;
        }
        this.mainPresenter.refreshAll(); // Need to refresh status as well
        return true;
    }

    /**
     * Handles the payment type deletion event.
     *
     * @param paymentType the {@code PaymentType} to be deleted.
     */
    public void handleDeletePaymentType(PaymentType paymentType) {
        logger.debug("Handle delete payment type: {}", paymentType);
        this.expenseService.deletePaymentType(paymentType.getId());

        // Switch to all expenses if we're deleting the currently viewed payment type.
        if (this.model.getExpenseFilter().getClass().equals(PaymentTypeExpenseFilter.class)
                && ((PaymentTypeExpenseFilter) this.model.getExpenseFilter()).getPaymentType().equals(paymentType)) {
            handleAllCategories();
        }
        this.mainPresenter.refreshAll(); // Need to refresh status as well
    }

    //@author A0081058H
    /**
     * Handles input from search box in {@code MainPresenter}.
     */
    public void handleSearchBoxInput(String input) {
        logger.debug("Handle search input: {}", input);
        // Treat blank input the same as finding all expenses
        if (input.trim().isEmpty()) {
            handleAllCategories();
        } else {
            ExpenseFilter filter;
            try {
                filter = new MultiFilter(input, expenseService);
            } catch (Exception e) {
                filter = new TextExpenseFilter(input);
                // e.printStackTrace();
            }
            changeExpenseFilter(filter);
        }
        refresh();
    }

    /**
     * Handles the edit expense event. Brings up the expense details window for the {@code Expense} to be edited.
     *
     * @param expense the {@code Expense} to be edited.
     */
    public void handleEditExpense(Expense expense) {
        logger.debug("Handle edit expense: {}", expense);
        this.mainPresenter.handleEditExpense(expense);
        this.mainPresenter.refreshAll(); // Need to refresh status as well
    }

    /**
     * Handles the delete expense event.
     *
     * @param expense the {@code Expense} to be deleted.
     */
    public void handleDeleteExpense(Expense expense) {
        logger.debug("Handle delete expense: {}", expense);
        this.expenseService.deleteExpense(expense.getId());
        this.mainPresenter.refreshAll();
    }

    /**
     * Handles dragging of expenses to categories. Basically updates the given expense with the given category.
     *
     * @param expense the {@code Expense} to update.
     * @param category the {@code Category} to update the given {@code Expense} to.
     */
    public void handleDragExpenseToCategory(Expense expense, Category category) {
        logger.debug("Handle dragged expense to category: {}", category);
        final Expense updatedExpense = new Expense(expense.getId(), expense.getName(), expense.getDetails(),
                expense.getPrice(), category, expense.getPaymentType(), expense.getDate());
        this.expenseService.updateExpense(updatedExpense);
        this.mainPresenter.refreshAll(); // Need to refresh status as well
    }

    //@author A0081189X
    /**
     * Handles dragging of expenses to payment types. Basically updates the given expense with the given payment type.
     *
     * @param expense the {@code Expense} to update.
     * @param paymentType the {@code PaymentType} to update the given {@code Expense} to.
     */
    public void handleDragExpenseToPaymentType(Expense expense, PaymentType paymentType) {
        logger.debug("Handle dragged expense to payment type: {}", paymentType);
        final Expense updatedExpense = new Expense(expense.getId(), expense.getName(), expense.getDetails(),
                expense.getPrice(), expense.getCategory(), paymentType, expense.getDate());
        this.expenseService.updateExpense(updatedExpense);
        this.mainPresenter.refreshAll(); // Need to refresh status as well
    }

    //@author A0081058H
    /**
     * Handles the event for editing of the selected expense in the expense list.
     */
    public void handleEditSelectedExpense() {
        Expense selectedExpense = this.view.getListView().getSelectionModel().getSelectedItem();
        logger.debug("Handle edit selected expense - currently selected: {}", selectedExpense);
        handleEditExpense(selectedExpense);
    }

    /**
     * Handles the event for deletion of the selected expense in the expense list.
     */
    public void handleDeleteSelectedExpense() {
        Expense selectedExpense = this.view.getListView().getSelectionModel().getSelectedItem();
        logger.debug("Handle delete selected expense - currently selected: {}", selectedExpense);
        handleDeleteExpense(selectedExpense);
    }

    /**
     * Refreshes the {@code ListView} by performing a new search.
     */
    public void refresh() {
        this.model.setExpenseList(this.expenseService.getExpenses(this.model.getExpenseFilter()));
        this.view.updateCategoryList();
    }

    /**
     * Sets the {@code ExpenseFilter} for the view.
     *
     * @param filter the {@code ExpenseFilter} for the expense list.
     */
    private void changeExpenseFilter(ExpenseFilter filter) {
        this.model.setExpenseFilter(filter);
        this.model.setListHeaderText(filter.getDescription());
    }
}
